#include "Subsystems/BlockBehaviors/SubsystemCampfireBlockBehavior.h"
#include "Log.h"
#include "GameSingleton.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Subsystems/SubsystemTimeOfDay.h"
#include "Subsystems/SubsystemWeather.h"
#include "Subsystems/SubsystemExplosions.h"

#include "Components/ComponentMiner.h"

#include "Managers/BlocksManager.h"
#include "Blocks/SnowBlock.h"
#include "Blocks/SnowballBlock.h"
#include "Blocks/IceBlock.h"
#include "cppcodec/base64_default_url.hpp"
#include "Network/PackageReader.h"
#include "Network/PackageWriter.h"
#include "GameRandom.hpp"

namespace PocketSurvival
{

    SubsystemCampfireBlockBehavior::SubsystemCampfireBlockBehavior() {}

    bool SubsystemCampfireBlockBehavior::addFuel(int32_t x, int32_t y, int32_t z, int32_t value, int32_t count)
    {
        if (Terrain::ExtractData(m_terrain->getCellValue(x, y, z)) > 0)
        {
            int32_t num = Terrain::ExtractContents(value);
            Block *block = GameSingleton::blocksManager->blocks[num];
            if (m_explosions->tryExplodeBlock(x, y, z, value))
            {
                return true;
            }
            if (block->isBlock<SnowBlock *>() || block->isBlock<SnowballBlock *>() || block->isBlock<IceBlock *>())
            {
                return resizeCampfire(x, y, z, -1);
            }
            if (block->FuelHeatLevel > 0.0f)
            {
                float num2 = count * MathUtils::Min<float>(block->FuelFireDuration, 20.0f) / 5.0f;
                int32_t num3 = (int32_t)num2;
                float num4 = num2 - num3;
                if (GameSingleton::gameRandom->getFloat(0.0f, 1.0f) < num4)
                {
                    num3++;
                }
                if (num3 > 0)
                {
                    return resizeCampfire(x, y, z, num3);
                }
                return true;
            }
        }
        return false;
    }

    bool SubsystemCampfireBlockBehavior::resizeCampfire(int32_t x, int32_t y, int32_t z, int32_t steps)
    {
        int32_t cellValue = m_terrain->getCellValue(x, y, z);
        int32_t num = Terrain::ExtractData(cellValue);
        if (num > 0)
        {
            int32_t num2 = MathUtils::Clamp(num + steps, 0, 15);
            if (num2 != num)
            {
                int32_t value = Terrain::ReplaceData(cellValue, num2);
                m_terrain->changeCell(x, y, z, value);
                return true;
            }
        }
        return false;
    }

    void SubsystemCampfireBlockBehavior::addCampfire(int32_t value, int32_t x, int32_t y, int32_t z)
    {
        int32_t num = Terrain::ExtractData(value);
        if (num > 0)
        {
            m_campfireSetLock.lock();
            m_campfireSet.emplace(x, y, z);
            m_campfireSetLock.unlock();
        }
    }

    void SubsystemCampfireBlockBehavior::removeCampfire(int32_t x, int32_t y, int32_t z)
    {
        Point3 point(x, y, z);
        m_campfireSetLock.lock();
        auto it = m_campfireSet.find(point);
        if (it != m_campfireSet.end())
        {
            m_campfireSet.erase(it);
        }
        m_campfireSetLock.unlock();
    }

    void SubsystemCampfireBlockBehavior::onNeighborBlockChanged(int32_t x, int32_t y, int32_t z, int32_t neighborX, int32_t neighborY, int32_t neighborZ)
    {
        int32_t cellContents = m_terrain->getCellContents(x, y - 1, z);
        if (GameSingleton::blocksManager->blocks[cellContents]->IsTransparent)
        {
            m_terrain->destroyCell(0, x, y, z, 0, false);
        }
    }

    void SubsystemCampfireBlockBehavior::onChunkDiscarding(TerrainChunk *chunk)
    {
        std::vector<Point3> list;
        int32_t startX = chunk->origin.posX;
        int32_t startZ = chunk->origin.posY;
        int32_t endX = chunk->origin.posX + 16;
        int32_t endZ = chunk->origin.posY + 16;
        for (auto it = m_campfireSet.begin(); it != m_campfireSet.end(); ++it)
        {
            if (it->posX >= startX && it->posX < endX && it->posZ >= startZ && it->posZ < endZ)
            {
                list.push_back(*it);
            }
        }
        for (auto it = list.begin(); it != list.end(); ++it)
        {
            resizeCampfire(it->posX, it->posY, it->posZ, -15);
            removeCampfire(it->posX, it->posY, it->posZ);
        }
    }

    bool SubsystemCampfireBlockBehavior::onInteract(const TerrainRaycastResult &raycastResult, ComponentMiner *componentMiner)
    {
        if (addFuel(raycastResult.cellFace.point, componentMiner->getActiveBlockValue(), 1))
        {
            componentMiner->removeActiveTool(1);
        }
        return true;
    }

    void SubsystemCampfireBlockBehavior::onHitByProjectile(const CellFace &cellFace, WorldItem *worldItem)
    {
        if(worldItem->toRemove == false)
        {
            int32_t count = (worldItem->type == WorldItemType::Pickable ? static_cast<Pickable*>(worldItem)->count : 1);
            if(addFuel(cellFace.point, worldItem->value, count))
            {
                worldItem->toRemove = true;
            }
        }
    }

    void SubsystemCampfireBlockBehavior::onBlockAdded(int32_t value, int32_t oldValue, int32_t x, int32_t y, int32_t z)
    {
        addCampfire(value, x, y, z);
    }

    void SubsystemCampfireBlockBehavior::onBlockRemoved(int32_t value, int32_t newValue, int32_t x, int32_t y, int32_t z)
    {
        removeCampfire(x, y, z);
    }

    void SubsystemCampfireBlockBehavior::onBlockModified(int32_t value, int32_t oldValue, int32_t x, int32_t y, int32_t z)
    {
        removeCampfire(x, y, z);
        addCampfire(value, x, y, z);
    }

    void SubsystemCampfireBlockBehavior::update(float dt)
    {
        if(m_timeOfDay->periodicGameTimeEvent(5.0, 0.0))
        {
            for(auto it = m_campfireSet.begin(); it != m_campfireSet.end(); ++it)
            {
                PrecipitationShaftInfo precipitationShaftInfo = m_weather->getPrecipitationShaftInfo(it->posX, it->posZ);
                if((precipitationShaftInfo.Intensity > 0.0f && it->posY >= precipitationShaftInfo.YLimit -1) || GameSingleton::gameRandom->getBool(0.2f))
                {
                    m_toReduce.push_back(*it);
                }
            }
            for(auto it = m_toReduce.begin(); it != m_toReduce.end(); ++it)
            {
                resizeCampfire(it->posX, it->posY, it->posZ, -1);
            }
            m_toReduce.clear();
        }
    }

    void SubsystemCampfireBlockBehavior::load(const nlohmann::json &json)
    {
        m_terrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();
        m_weather = GameSingleton::singletonPtr->getSingleton<SubsystemWeather>();
        m_timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();
        m_explosions = GameSingleton::singletonPtr->getSingleton<SubsystemExplosions>();

        auto it = json.find("data");
        if(it != json.end())
        {
            std::vector<uint8_t> dataVec = cppcodec::base64_url::decode(it->get<std::string>());
            uint32_t buffLength = json["size"].get<uint32_t>();

            char *buff = new char[buffLength];

            uint32_t dataSize = Utils::RawInflateDecompresse((const char*)dataVec.data(), dataVec.size(), buff, buffLength);
            PackageReader reader(buff, dataSize);

            uint32_t campfireCount = reader.readUInt32();

            m_campfireSetLock.lock();
            for(uint32_t index = 0; index < campfireCount; ++index)
            {
                Point3 pos;
                reader.readPoint3(pos);
                m_campfireSet.insert(pos);
            }
            m_campfireSetLock.unlock();

            delete[] buff;
        }
    }

    void SubsystemCampfireBlockBehavior::save(nlohmann::json& json)
    {
        m_campfireSetLock.lock();
        if(m_campfireSet.size() > 0)
        {
            PackageWriter writer;
            writer.writeUInt32(m_campfireSet.size());
            for(auto it = m_campfireSet.begin(); it != m_campfireSet.end(); ++it)
            {
                writer.writePoint3(*it);
            }

            uint32_t buffLength = writer.getPosition() + 64;
            char *buff = new char[buffLength];
            uint32_t buffSize = Utils::RawDeflateCompresse(writer.getBuffer(), writer.getPosition(), buff, buffLength);

            json["data"] = cppcodec::base64_url::encode(buff, buffSize);
            json["size"] = writer.getPosition();

            delete[] buff;
        }
        m_campfireSetLock.unlock();
    }

    static const std::string subsystemName = "CampfireBlockBehavior";
    const std::string &SubsystemCampfireBlockBehavior::getName() const
    {
        return subsystemName;
    }
    SubUpdateType SubsystemCampfireBlockBehavior::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }
}